﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;
using Artemis;

namespace JAWWS
{
    public struct MD2Header
    {
        public int ID;			// File Type - Normally 'IPD2'
        public int Version;
        public int TexWidth;   // Texture width
        public int TexHeight;  // Texture height 
        public int FrameSize;  // Size for frames in bytes
        public int nTextures;  // Number of textures
        public int nVertices;  // Number of vertices
        public int nTexCoords; // Number of UVs
        public int nTriangles; // Number of polys
        public int nGLCmd;     // Number of GL Commmands
        public int nFrames;    // Number of frames
        public int TexOffset;  // Offset to texture name(s)
        public int UVOffset;   // Offset to UV data
        public int FaceOffset; // Offset to poly data
        public int FrameOffset;// Offset to first frame  
        public int GLCmdOffset;// Offset to GL Cmds 
        public int EOFOffset;  // Size of file
    };
    public class MD2Face
    {
        public short p1, p2, p3;
        public short uv1, uv2, uv3;
    };
    public class MD2Vtx
    {
        public byte[] Vtx = new byte[3];
        public byte lNorm;
    };
    public class Mesh_Vtx
    {
        public float x, y, z;
    };
    public class Mesh_UV
    {
        public float u, v;
    };
    public class MD2Frame
    {
        public float[] Scale = new float[3];
        public float[] Translate = new float[3];
        public string Name;
        public Mesh_Vtx[] TrueVertex;
        public MD2CompressedVertex[] compressedVertexList;
    };
    public class MD2CompressedVertex
    {
        public byte[] v = new byte[3];
        public byte lightNormalIndex;
    };
    public class MD2TecCoord
    {
        public short u, v;
    };
    public class Animation
    {
        public AnimationName[] animationName = new AnimationName[8];
    }
    public class AnimationName
    {
        public string name;
        public int startFrame;
        public int endFrame;
        public int curFrame;
        public int nextFrame;
    }
    public class MD2Obj
    {
        public static readonly int MD2_OK           = 0x0;
        public static readonly int MD2_ERR_MEM      = 0x1;
        public static readonly int MD2_ERR_FILE     = 0x2;
        public static readonly int MD2_ERR_FORMAT   = 0x4;

        private int nFrames, nTri, nVtx, nUV;
        private MD2Face[] Face;
        private MD2Frame[] Frame;
        private Mesh_UV[] UV;
        private string[] TexName;
        private uint TexID;
        private string Pathname;
        private string TexFile;
        private string Name;
        private Timer frameTimer = new Timer();
        private int currentFrame;
        private int nextFrame;
        private float frameFraction;
        private char[] NumCheck = new char[10] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
        private int index;
        private float[] Position = new float[3];
        private bool inAnimation = false;
        private int nextFrameTime;
        private Animation animation = new Animation();       

        public int LoadFile(string filename)
        {
            Pathname = filename;
            FileStream fs = new FileStream(filename, FileMode.Open);
            BinaryReader br = new BinaryReader(fs);

            // read header
            MD2Header header = new MD2Header();

            // Read basic ints from header
            header.ID = br.ReadInt32();
            header.Version = br.ReadInt32();
            header.TexWidth = br.ReadInt32();
            header.TexHeight = br.ReadInt32();
            header.FrameSize = br.ReadInt32();
            header.nTextures = br.ReadInt32();
            header.nVertices = br.ReadInt32();
            header.nTexCoords = br.ReadInt32();
            header.nTriangles = br.ReadInt32();
            header.nGLCmd = br.ReadInt32();
            header.nFrames = br.ReadInt32();
            header.TexOffset = br.ReadInt32();
            header.UVOffset = br.ReadInt32();
            header.FaceOffset = br.ReadInt32();
            header.FrameOffset = br.ReadInt32();
            header.GLCmdOffset = br.ReadInt32();
            header.EOFOffset = br.ReadInt32();
            // etc.

            // For use later
            nFrames = header.nFrames;
            nTri = header.nTriangles;
            nVtx = header.nVertices;
            nUV = header.nTexCoords;

            Console.WriteLine("Header Data\n");
            Console.WriteLine("ID:                            " + header.ID);
            Console.WriteLine("Version:                       " + header.Version);
            Console.WriteLine("Texture Width:                 " + header.TexWidth);
            Console.WriteLine("Texture Height:                " + header.TexHeight);
            Console.WriteLine("Frame Size:                    " + header.FrameSize);
            Console.WriteLine("Number of textures:            " + header.nTextures);
            Console.WriteLine("Number of Vertices:            " + header.nVertices);
            Console.WriteLine("Number of Texture Coordinates: " + header.nTexCoords);
            Console.WriteLine("Number of Triangles:           " + header.nTriangles);
            Console.WriteLine("Number of GL Commands:         " + header.nGLCmd);
            Console.WriteLine("Number of frames:              " + header.nFrames);
            Console.WriteLine("Texture Offset:                " + header.TexOffset);
            Console.WriteLine("UV Offset:                     " + header.UVOffset);
            Console.WriteLine("Poly Data Offset:              " + header.FaceOffset);
            Console.WriteLine("First Frame Offset:            " + header.FrameOffset);
            Console.WriteLine("GL Commands Offset:            " + header.GLCmdOffset);
            Console.WriteLine("Size of file Offset:           " + header.EOFOffset);

            // read texture names
            // there will be nTextures of them
            fs.Seek((long)header.TexOffset, SeekOrigin.Begin);
            this.TexName = new string[header.nTextures];
            for (int i = 0; i < header.nTextures; i++)
            {
                byte[] nameBuf = br.ReadBytes(64);
                StringBuilder buf = new StringBuilder();
                for (int j = 0; j < 64; j++)
                {
                    if (nameBuf[j] == 0)
                    {
                        break;
                    }
                    buf.Append((char)nameBuf[j]);
                }
                this.TexName[i] = buf.ToString();
            }
          
            Console.WriteLine("Texture Names:");
            for (int i = 0; i < header.nTextures; i++)
            {
                Console.WriteLine(i + ": " + this.TexName[i]);
            }

            // read UV coordinate pairs;
            // there will be nTexCoords of them
            fs.Seek((long)header.UVOffset, SeekOrigin.Begin);
            this.UV = new Mesh_UV[header.nTexCoords];
            for (int i = 0; i < header.nTexCoords; i++)
            {
                // Read a single UV coordinate pair
                UV[i] = new Mesh_UV();
                UV[i].u = br.ReadInt16()/(float)header.TexWidth;
                UV[i].v = br.ReadInt16()/(float)header.TexHeight;
            }
          
            //Console.WriteLine("UV Coordinate Pairs:");
            //for (int i = 0; i < header.nTexCoords; i++)
            //{
            //    Console.WriteLine(i + ": U = " + UV[i].u + "; V = " + UV[i].v);
            //}

            // Read the Triangle Data
            // there will be nTriangles of them
            fs.Seek((long)header.FaceOffset, SeekOrigin.Begin);
            this.Face = new MD2Face[header.nTriangles];
            for (int i = 0; i < header.nTriangles; i++)
            {
                // read a single Triangle
                Face[i] = new MD2Face();
                // Read the 3 indicies for the triangle
                Face[i].p1 = br.ReadInt16();
                Face[i].p2 = br.ReadInt16();
                Face[i].p3 = br.ReadInt16();
                // read the 3 UV coordinates for the triangle
                Face[i].uv1 = br.ReadInt16();
                Face[i].uv2 = br.ReadInt16();
                Face[i].uv3 = br.ReadInt16();
            }
            // Read the Frames!
            // there will be nframes of them
            fs.Seek((long)header.FrameOffset, SeekOrigin.Begin);
            this.Frame = new MD2Frame[header.nFrames];
            for (int i = 0; i < header.nFrames; i++)
            {
                // Read the Frame info
                Frame[i] = new MD2Frame();
                // Read the Scale
                Frame[i].Scale[0] = br.ReadSingle();
                Frame[i].Scale[1] = br.ReadSingle();
                Frame[i].Scale[2] = br.ReadSingle();
                // Read the translate
                Frame[i].Translate[0] = br.ReadSingle();
                Frame[i].Translate[1] = br.ReadSingle();
                Frame[i].Translate[2] = br.ReadSingle();
                // Read the name
                byte[] nameBuf = br.ReadBytes(16);
                StringBuilder buf = new StringBuilder();
                for (int j = 0; j < 16; j++)
                {
                    if (nameBuf[j] == 0)
                    {
                        break;
                    }
                    buf.Append((char)nameBuf[j]);
                }
                Frame[i].Name = buf.ToString(); 
                // Read the compressed verticies
                Frame[i].compressedVertexList = new MD2CompressedVertex[header.nVertices];
                for (int j = 0; j < header.nVertices; j++)
                {
                    Frame[i].compressedVertexList[j] = new MD2CompressedVertex();
                    Frame[i].compressedVertexList[j].v[0] = br.ReadByte();
                    Frame[i].compressedVertexList[j].v[1] = br.ReadByte();
                    Frame[i].compressedVertexList[j].v[2] = br.ReadByte();
                    Frame[i].compressedVertexList[j].lightNormalIndex = br.ReadByte();
                }
                // uncompress verticies
                Frame[i].TrueVertex = new Mesh_Vtx[header.nVertices];
                for (int j = 0; j < header.nVertices; j++)
                {
                    Frame[i].TrueVertex[j] = new Mesh_Vtx();
                    Frame[i].TrueVertex[j].x = (Frame[i].compressedVertexList[j].v[0] * Frame[i].Scale[0]) + Frame[i].Translate[0];
                    Frame[i].TrueVertex[j].y = (Frame[i].compressedVertexList[j].v[1] * Frame[i].Scale[1]) + Frame[i].Translate[1];
                    Frame[i].TrueVertex[j].z = (Frame[i].compressedVertexList[j].v[2] * Frame[i].Scale[2]) + Frame[i].Translate[2];
                }
            }

            Console.WriteLine("Frame Data:");
            for (int i = 0; i < header.nFrames; i++)
            {
                Console.WriteLine("Frame " + i + " name: " + Frame[i].Name);
            }

            // Calculate Normals
            for (int i = 0; i < header.nFrames; i++)
            {
                for (int j = 0; j < header.nTriangles; j++)
                {
                    CalcNormals(Frame[i].TrueVertex[Face[j].p1],
                        Frame[i].TrueVertex[Face[j].p2],
                        Frame[i].TrueVertex[Face[j].p3]);
                }
            }
            fs.Close();
            br.Close();
            return MD2_OK;
        }
        public void CalcNormals(Mesh_Vtx v1, Mesh_Vtx v2, Mesh_Vtx v3)
        {
            float v1x, v1y, v1z, v2x, v2y, v2z;
            float nx, ny, nz;
            float vLen;

            // Calculate Vectors
            v1x = v1.x - v2.x;
            v1y = v1.y - v2.y;
            v1z = v1.z - v2.z;

            v2x = v2.x - v3.x;
            v2y = v2.y - v3.y;
            v2z = v2.z - v3.z;

            // Get The Cross-Product of Vectors
            nx = (v1y * v2z) - (v1z * v2y);
            ny = (v1z * v2x) - (v1x * v2z);
            nz = (v1x * v2y) - (v1y * v2x);

            // NORMALISE
            vLen = (float)Math.Sqrt((nx * nx) + (ny * ny) + (nz * nz));
        }
        public void loadAnimation(string name)
        {
            int currentAnimation = 0;
            if (name != "spare")
            {
                for (int i = 0; i < 8; i++)
                {
                    if (animation.animationName[i] == null)
                    {
                        animation.animationName[i] = new AnimationName();
                        animation.animationName[i].name = name;
                        currentAnimation = i;
                        break;
                    }
                }
                inAnimation = true;
                for (int j = 0; j < nFrames; j++)
                {
                    if (Frame[j].Name.Length == name.Length)
                    {
                        if (String.Compare(Frame[j].Name, name, true) == 0)
                        {
                            animation.animationName[currentAnimation].startFrame = j;
                            animation.animationName[currentAnimation].curFrame = j;
                            animation.animationName[currentAnimation].nextFrame = animation.animationName[currentAnimation].curFrame + 1;
                        }
                    }
                }
                for (int j = animation.animationName[currentAnimation].startFrame; j < nFrames; j++)
                {
                    if (inAnimation == true)
                    {
                        if (Frame[j].Name.Contains(name.Remove(3)))
                        {
                            animation.animationName[currentAnimation].endFrame = j;
                            inAnimation = true;
                        }
                        else
                        {
                            inAnimation = false;
                        }
                    }
                }
            }
        }
        public AnimationName getAnimation(string name)
        {
            for(int i = 0; i < 8; i++)
            {
                if (animation.animationName[i].name == name)
                {
                    return animation.animationName[i];
                }
            }
            return null;
        }
        public void SetTexture(uint tex)
        {
            this.TexID = tex;
        }
        public string getPathname()
        {
            return Pathname;
        }
        public void setPathname(string path)
        {
            Pathname = path;
        }
        public string getName()
        {
            return Name;
        }
        public void setName(string name)
        {
            Name = name;
        }
        public int getCurrentFrame()
        {
            return currentFrame;
        }
        public void setCurrentFrame(int i)
        {
            currentFrame = i;
        }
        public int getNextFrame()
        {
            return nextFrame;
        }
        public void setNextFrame(int i)
        {
            nextFrame = i;
        }
        public float getFrameFraction()
        {
            return frameFraction;
        }
        public int getStartFrame(int index)
        {
            return animation.animationName[index].startFrame;
        }
        public int getEndFrame(int index)
        {
            return animation.animationName[index].endFrame;
        }
        public void setPosition(float[] pos)
        {
            Position = pos;
        }
        public void drawFrame(int frame, int nframe, float frac, uint Tex)
        {
            // Limit Frame range
            if (frame >= nframe)
            {
                frame = 0;
            }
            GL.glEnable(GLenum.GL_TEXTURE_2D);
            GL.glDisable(GLenum.GL_LIGHTING);
            GL.glColor4f(1, 1, 1, 1);
            GL.glBindTexture(GLenum.GL_TEXTURE_2D, Tex);
            GL.glScalef(.05f, .05f, .05f);
            GL.glRotatef(-90f, 1f, 0f, 0f);
            for (int i = 0; i < nTri; i++)
            {
                GL.glBegin(GLenum.GL_TRIANGLES);
                GL.glTexCoord2f(UV[Face[i].uv1].u, UV[Face[i].uv1].v);
                GL.glVertex3f(((Frame[frame].TrueVertex[Face[i].p1].x * (frac)) + Frame[nframe].TrueVertex[Face[i].p1].x * (1 - frac)), (Frame[frame].TrueVertex[Face[i].p1].y * (frac) + Frame[nframe].TrueVertex[Face[i].p1].y * (1 - frac)), (Frame[frame].TrueVertex[Face[i].p1].z + Frame[nframe].TrueVertex[Face[i].p1].z * (1 - frac)));
                GL.glTexCoord2f(UV[Face[i].uv2].u, UV[Face[i].uv2].v);
                GL.glVertex3f(((Frame[frame].TrueVertex[Face[i].p2].x * (frac)) + Frame[nframe].TrueVertex[Face[i].p2].x * (1 - frac)), (Frame[frame].TrueVertex[Face[i].p2].y * (frac) + Frame[nframe].TrueVertex[Face[i].p2].y * (1 - frac)), (Frame[frame].TrueVertex[Face[i].p2].z + Frame[nframe].TrueVertex[Face[i].p2].z * (1 - frac)));
                GL.glTexCoord2f(UV[Face[i].uv3].u, UV[Face[i].uv3].v);
                GL.glVertex3f(((Frame[frame].TrueVertex[Face[i].p3].x * (frac)) + Frame[nframe].TrueVertex[Face[i].p3].x * (1 - frac)), (Frame[frame].TrueVertex[Face[i].p3].y * (frac) + Frame[nframe].TrueVertex[Face[i].p3].y * (1 - frac)), (Frame[frame].TrueVertex[Face[i].p3].z + Frame[nframe].TrueVertex[Face[i].p3].z * (1 - frac)));
                GL.glEnd();
            }
            GL.glDisable(GLenum.GL_TEXTURE_2D);
            GL.glEnable(GLenum.GL_LIGHTING);
        }
    }
}
